package lang.asm;

import lang.Encoder;
import lang.Language;
import lang.Parser;
import model.Segment;
import model.code.Function;
import model.code.GlobalVariable;
import model.code.Structure;
import model.code.CodeFileChunk;
import model.code.line.*;
import model.symbol.*;
import util.ASUtil;
import util.ByteList;
import util.IntList;
import util.StringUtil;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * This is the parser of assembly language code.
 *
 * @author Neil Dickson
 */
public class ASMParse extends Parser {
	public static final ASMParse instance = new ASMParse();

	public static final int		DEFAULT_RADIX = 10;

	public static final char	RADIX_2_SUFFIX		= 'b';	// B for Binary
	public static final char	RADIX_2_SUFFIX2		= 'y';	// Y because B is a digit if default radix is hexadecimal
	public static final int		RADIX_8_SUFFIX		= 'o';	// O for Octal
	public static final int		RADIX_8_SUFFIX2		= 'q';	// Q because it doesn't look as much like zero as O does
	public static final int		RADIX_10_SUFFIX		= 'd';	// D for Decimal
	public static final int		RADIX_10_SUFFIX2	= 't';	// T because D is a digit if default radix is hexadecimal
	public static final int		RADIX_16_SUFFIX		= 'h';	// H for Hexadecimal
	public static final int		RADIX_FLOAT_SUFFIX	= 'r';	// R for Real

	private ASMParse() {

	}

	public String trimComment(String comment) {
		comment = comment.trim();
		if (comment.length()==0) {
			return comment;
		}
		if (comment.charAt(0)==';') {
			return comment.substring(1).trim();
		}
		if (comment.regionMatches(true,0,"comment",0,"comment".length()) && ("comment".length()>=comment.length() || !StringUtil.isWordChar(comment.charAt("comment".length()),ASM.WORD_SPECIAL_CHARS))) {
			int afterSpace = StringUtil.getIndexAfterSpace(comment,"comment".length());
			if (afterSpace>=comment.length()) {
				return "";
			}
			char endChar = comment.charAt(afterSpace);
			int endIndex = comment.indexOf(endChar,afterSpace+1);
			if (endIndex<0) {
				return comment.substring(afterSpace+1).trim();
			}
			// Don't need to trim the 2nd half, since it's already trimmed on the right
			return comment.substring(afterSpace+1,endIndex).trim()+comment.substring(endIndex+1);
		}
		return comment;
	}

	/**
	 * Tokenizes the line as a line of assembly language.
	 * @param line either an ASMLine, or a Line to be tokenized as one
	 */
	public void tokenizeLine(Line line) {
		HashMap<Register, DataType> assumptions = (line instanceof ASMLine) ? ((ASMLine)line).getAssumptions() : ASMLine.DUMMY_ASSUMPTIONS;
		CodeFileChunk parent = line.getList().getParent();
		Scope scope = parent.getScope();
		int currentMode = parent.getMode();
		char[] s = line.getText();
		int length = line.length();
		// NOTE: function.getLanguage() may be null, so use the language directly.
		Scope languageScope = ASM.language.getScope();

		int i=0;
		// First, skip initial whitespace
		while (i<length && isWhiteSpace(s[i])) {
			++i;
		}
		if (i>0) {
			line.addReference(Line.WHITE_SPACE_MARKER,0,false);
		}
		// Second, handle line labels
		int wordLength = getWordLength(s,length,i,true);
		if ((parent instanceof Function) && wordLength>0 && (i+wordLength < length) && s[i+wordLength]==':') {
			LineLabel label;
			char[] word = ASUtil.subCharArray(s,i,i+wordLength);
			label = new LineLabel(word,Line.EMPTY_CHAR_ARRAY,(Function)parent);
			line.addReference(label,i,true);
			line.addReference(Line.OPERATOR_MARKER,i+wordLength,false);
			i += wordLength+1;	// skip over the label and the colon
		}


		// Then handle the rest of the line
		boolean haveFirstWord = false;
		// NOTE: haveMnemonic can also become true if a constant or function is encountered,
		// because a mnemonic isn't useful in that context, and "and" & "or" are useful as directives in that case
		boolean haveMnemonic = false;
		boolean startedComment = false;
		Structure currentStructureType = null;
		Structure temporaryStructureType = null;
		Object lastMajorRef = null;
		while (i<length) {
			int start = i;
			while (i<length && isWhiteSpace(s[i])) {
				++i;
			}
			if (i>start) {
				line.addReference(Line.WHITE_SPACE_MARKER,start,false);
			}
			if (i<length) {
				boolean tokenAdded = false;
				if (isDigit(s[i])) {
					int numberLength = getNumberLength(s,length,i,DEFAULT_RADIX);
					if (numberLength!=Integer.MIN_VALUE) {
						Number number = getNumber(s,i,i+Math.abs(numberLength),DEFAULT_RADIX,numberLength<0);
						line.addReference(number,i,false);
						i += Math.abs(numberLength);
						tokenAdded = true;
						// NOTE: There isn't actually a mnemonic, but we don't want to treat anything as a mnemonic after seeing a constant.
						haveMnemonic = true;
					}
				}
				if (!tokenAdded && !isWordChar(s[i]) && (haveFirstWord || s[i]!='.')) {
					++i;
					if (s[i-1]==';') {
						line.addReference(Line.OPERATOR_MARKER,i-1,false);
						startedComment = true;
						currentStructureType = null;
						temporaryStructureType = null;
					}
					else if (!startedComment) {
						// TODO: Handle '<' as a quotation mark where appropriate
						if (s[i-1]=='\"' || s[i-1]=='\''/* || s[i-1]=='<'*/) {
							char quotationMark = s[i-1];
							/*if (quotationMark=='<') {
								quotationMark = '>';
							}*/
							start = i-1;
							// NOTE: If the starting quotation mark type appears twice in a row, it's a character in the string, not the ending mark.
							boolean isDuplicatedQuote;
							do {
								while (i<length && s[i]!=quotationMark) {
									++i;
								}
								isDuplicatedQuote = false;
								if (i+1<length && s[i+1]==quotationMark) {
									i+=2;
									isDuplicatedQuote = true;
								}
							} while (isDuplicatedQuote);
							line.addReference(Line.STRING_LITERAL_MARKER,start,false);
							if (i<length) {
								++i;
							}
						}
						else {
							line.addReference(Line.OPERATOR_MARKER,i-1,false);
							if (s[i-1]==',') {
								currentStructureType = null;
								temporaryStructureType = null;
							}
						}
						// NOTE: There isn't actually a mnemonic, but we don't want to treat anything as a mnemonic after seeing a constant.
						haveMnemonic = true;
					}
					else {
						line.addReference(Line.OPERATOR_MARKER,i-1,false);
					}
				}
				else {
					wordLength = getWordLength(s,length,i,!haveFirstWord);	// First word can start with '.'
					if (wordLength>0) {
						Symbol wordSymbol = null;
						Structure activeStructure = (temporaryStructureType!=null) ? temporaryStructureType : currentStructureType;
						if (!startedComment) {				// Don't check for symbols inside comments
							String word = new String(s,i,wordLength);
							ArrayList<Symbol> symbols = languageScope.get(word.toLowerCase());
							if (symbols!=null && symbols.size()>0) {
								for (Symbol symbol : symbols) {
									if (haveMnemonic) {
										if (!(symbol instanceof Mnemonic)) {
											wordSymbol = symbol;
											break;
										}
									}
									else if (symbol instanceof Mnemonic) {
										wordSymbol = symbol;
										break;
									}
									else {
										wordSymbol = symbol;
										// NOTE: Don't break on this one, because if there is a mnemonic, we want it
									}
								}
							}
							if (wordSymbol==null) {
								if (activeStructure!=null) {
									symbols = activeStructure.getScope().get(word);
									if (symbols!=null && symbols.size()>0) {
										wordSymbol = symbols.get(0);
									}
								}
								if (wordSymbol==null) {
									symbols = (scope!=null) ? scope.get(word) : null;
									if (symbols!=null && symbols.size()>0) {
										wordSymbol = symbols.get(0);
									}
								}
							}
						}

						if (wordSymbol!=null) {
							if ((wordSymbol instanceof Mnemonic && !((Mnemonic)wordSymbol).isPrefix(languageScope)) || (wordSymbol instanceof Keyword && ((Keyword)wordSymbol).isLineStart())) {
								haveMnemonic = true;
							}
							else if (wordSymbol instanceof Constant || wordSymbol instanceof Function) {
								// NOTE: There isn't actually a mnemonic, but we don't want to treat anything as a mnemonic after seeing a function or constant.
								haveMnemonic = true;
							}
							else if ((wordSymbol instanceof Register) && assumptions.containsKey(wordSymbol)) {
								DataType type = assumptions.get(wordSymbol);
								currentStructureType = (type instanceof Structure) ? (Structure)type : null;
							}
							else if (wordSymbol instanceof Structure) {
								if (lastMajorRef!=null && (lastMajorRef==Directive.OFFSET || lastMajorRef==Directive.SIZEOF)) {
									temporaryStructureType = (Structure)wordSymbol;
								}
								else {
									currentStructureType = (Structure)wordSymbol;
								}
							}
							else if ((wordSymbol instanceof LocalVariable) || (wordSymbol instanceof GlobalVariable) || (wordSymbol instanceof StructureMember)) {
								DataType type;
								if (wordSymbol instanceof LocalVariable) {
									type = DataTypeUtil.getDataType(((LocalVariable)wordSymbol).getType().toString(),languageScope,scope,currentMode);
								}
								else if (wordSymbol instanceof GlobalVariable) {
									type = DataTypeUtil.getDataType(((GlobalVariable)wordSymbol).getDefaultType(),((GlobalVariable)wordSymbol).getLanguage().getScope(),scope,currentMode);
								}
								else { // wordSymbol instanceof StructureMember
									type = DataTypeUtil.getDataType(((StructureMember)wordSymbol).getType().toString(),(activeStructure!=null) ? activeStructure.getLanguage().getScope() : languageScope,scope,currentMode);
								}
								if (type instanceof Structure) {
									if (temporaryStructureType!=null || (lastMajorRef!=null && (lastMajorRef==Directive.OFFSET || lastMajorRef==Directive.SIZEOF))) {
										temporaryStructureType = (Structure)type;
									}
									else {
										currentStructureType = (Structure)type;
									}
								}
								else {
									if (temporaryStructureType!=null) {
										temporaryStructureType = null;
									}
									else if (!(lastMajorRef!=null && (lastMajorRef==Directive.OFFSET || lastMajorRef==Directive.SIZEOF))) {
										currentStructureType = null;
									}
								}
							}
						}
						// If we have something like "VECTOR ptr [eax].x", the ".x" is invalid if eax has no assumptions.
						// This is because the typecast is only the final data type, not the current one.
						// "[eax].VECTOR.x" would get element x of structure VECTOR.
						if (wordSymbol==ASM.PTR && currentStructureType!=null) {
							currentStructureType = null;
						}

						if ((wordSymbol instanceof Register) && assumptions.containsKey(wordSymbol)) {
							DataType type = assumptions.get(wordSymbol);
							currentStructureType = (type instanceof Structure) ? (Structure)type : null;
						}
						line.addReference(wordSymbol,i,false);
						tokenAdded = true;
						i += wordLength;
						haveFirstWord = true;
					}
				}
				if (tokenAdded && !line.isOperator(line.getNumReferences()-1,'.')) {
					lastMajorRef = line.getReferences()[line.getNumReferences()-1];
					if (!(lastMajorRef instanceof LocalVariable || lastMajorRef instanceof GlobalVariable || lastMajorRef instanceof Structure || lastMajorRef instanceof StructureMember)) {
						temporaryStructureType = null;
					}
				}
			}
		}
		line.trimToSize();
	}
	public static boolean isDigit(char c) {
		return (c>='0' && c<='9');
	}
	public static boolean isHexDigit(char c) {
		return (c>='0' && c<='9') || (c>='A' && c<='F') || (c>='a' && c<='f');
	}
	public static boolean isWordChar(char c) {
		return (c>='A' && c<='Z') || (c>='a' && c<='z') || (c>='0' && c<='9') || (c=='_') || (c=='$') || (c=='@') || (c=='?');
	}
	public static boolean isWhiteSpace(char c) {
		return (c<=' ');
	}

	public static String getWord(String from,int index) {
		return getWord(from,index,0,false);
	}
	public static String getWord(String from,int index,int startingAt,boolean allowDot) {
		int charIndex = startingAt;
		while (charIndex<from.length() && !(isWordChar(from.charAt(charIndex)) || (from.charAt(charIndex)=='.' && allowDot))) {
			++charIndex;
		}
		for (int i=0;i<index && charIndex<from.length();++i) {
			// Skip over the i^th word
			while (charIndex<from.length() && (isWordChar(from.charAt(charIndex)) || (from.charAt(charIndex)=='.' && allowDot))) {
				++charIndex;
			}
			// Skip over the space between words
			while (charIndex<from.length() && !(isWordChar(from.charAt(charIndex)) || (from.charAt(charIndex)=='.' && allowDot))) {
				++charIndex;
			}
		}
		if (charIndex>=from.length()) {
			return null;
		}
		int endCharIndex = charIndex+1;	// Know that word has at least one character
		// Find end of the word
		while (endCharIndex<from.length() && (isWordChar(from.charAt(endCharIndex)) || (from.charAt(endCharIndex)=='.' && allowDot))) {
			++endCharIndex;
		}
		return from.substring(charIndex,endCharIndex);
	}

	/**
	 * Finds the length of the word starting at the specified index (0 if at a whitespace character; -1 if past the end of line).
	 * @param from
	 * @param length
	 * @param startingAt
	 * @param allowDot
	 * @return
	 */
	public static int getWordLength(char[] from,int length,int startingAt,boolean allowDot) {
		if (startingAt>=length) {
			return -1;
		}
		if (!(isWordChar(from[startingAt]) || (from[startingAt]=='.' && allowDot))) {
			return 0;
		}
		int end = startingAt+1;	// Know that word has at least one character
		// Find end of the word
		while (end<length && isWordChar(from[end])) {
			++end;
		}
		return end-startingAt;
	}

	/**
	 * Finds the length of the number starting at the specified character index (Integer.MIN_VALUE if not a number; negative of length if floating-point)
	 * @param text
	 * @param length
	 * @param start the starting character of the number
	 * @param defaultRadix
	 * @return
	 */
	public static int getNumberLength(char[] text,int length,int start,int defaultRadix) {
		if (start>=length || !isDigit(text[start])) {
			return Integer.MIN_VALUE;
		}
		int end = start;
		while ((end < length) && isDigit(text[end])) {
			++end;
		}
		// Handle floating-point numbers
		if (end<length && text[end]=='.') {
			++end;
			while (end<length && isDigit(text[end])) {
				++end;
			}
			if (end<length && (text[end]|0x20)=='e') {
				++end;
				if (end<length && text[end]=='-') {
					++end;
				}
				if (end>=length) {
					// Must have an exponent when 'e' is present
					return Integer.MIN_VALUE;
				}
				while (end<length && isDigit(text[end])) {
					++end;
				}
			}
			// Not a number if word character follows the number
			// NOTE: start-end is negative of end-start, indicating that the number is floating-point
			return (end<length && isWordChar(text[end])) ? Integer.MIN_VALUE : start-end;
		}
		// Handle integers
		else {
			while (end<length && isHexDigit(text[end])) {
				++end;
			}
			char last = (end<length) ? (char)(text[end]|0x20) : 0;
			if (last<'a' || last>'z') {
				last = (char)(text[end-1]|0x20);
				if (last>='a' && last<='z') {
					--end;
				}
			}
			if (last>='a' && last<='z') {
				// If the last character is a valid suffix, the word includes the suffix.
				if (last==RADIX_2_SUFFIX2 || last==RADIX_8_SUFFIX || last==RADIX_8_SUFFIX2 || last==RADIX_10_SUFFIX2 || last==RADIX_16_SUFFIX ||
						(defaultRadix!=16 && (last==RADIX_2_SUFFIX || last==RADIX_10_SUFFIX))) {
					// Not a number if word character follows the number
					return (end+1<length && isWordChar(text[end+1])) ? Integer.MIN_VALUE : (end+1-start);
				}
			}
			// Not a number if word character follows the number
			return (end<length && isWordChar(text[end])) ? Integer.MIN_VALUE : end-start;
		}
	}
	public static strictfp Number getNumber(char[] text,int start,int end,int defaultRadix,boolean isFloatingPoint) {
		int radix = defaultRadix;
		// Handle floating-point numbers
		if (isFloatingPoint) {
			return Double.parseDouble(new String(text,start,end-start));
		}
		// Handle integers
		else {
			boolean radixChanged = false;
			char last = (char)(text[end-1]|0x20);
			if (last>='a' && last<='z') {
				if (last==RADIX_2_SUFFIX2) {
					radix = 2;
					radixChanged = true;
				}
				else if (last==RADIX_8_SUFFIX || last==RADIX_8_SUFFIX2) {
					radix = 8;
					radixChanged = true;
				}
				else if (last==RADIX_10_SUFFIX2) {
					radix = 10;
					radixChanged = true;
				}
				else if (last==RADIX_16_SUFFIX) {
					radix = 16;
					radixChanged = true;
				}
				else if (defaultRadix!=16) {
					if (last==RADIX_2_SUFFIX) {
						radix = 2;
						radixChanged = true;
					}
					else if (last==RADIX_10_SUFFIX) {
						radix = 10;
						radixChanged = true;
					}
				}
			}
			int endMinusSuffix = end - (radixChanged ? 1 : 0);
			// Each of these are unsigned dword values
			long valueLow = 0;
			long valueHigh = 0;
			for (int i=start;i<endMinusSuffix;++i) {
				int digit = getDigitValue(text[i]);
				if (digit<0 || digit>=radix) {
					// Not a number if digit too big for the radix
					return null;
				}

				valueLow = valueLow*radix + digit;
				long carry = (valueLow>>>32);
				valueLow &= 0x0FFFFFFFFL;
				valueHigh = valueHigh*radix + carry;
				// Not a number if larger than will fit in 64 bits
				if ((valueHigh>>>32)!=0) {
					return null;
				}
			}
			return new Long(valueLow+(valueHigh<<32));
		}
	}

	public static int getDigitValue(char c) {
		if (c>='0' && c<='9') {
			return c-'0';
		}
		c |= 0x20;
		if (c>='a' && c<='f') {
			return c-'a'+10;
		}
		return -1;
	}

	/**
	 * Performs Dijkstra's shunting algorithm on an immediate expression.
	 * @param line			the line containing the immediate expression to be evaluated
	 * @param startIndex	the starting token index (inclusive) of the immediate
	 * @param endIndex		the ending token index (exclusive) of the immediate
	 * @return an Immediate if the value is valid, null if there is an unknown token,
	 *         or {@link model.code.line.SyntaxError} if there is an error in the syntax
	 */
	public Object immediateShunting(Line line,int startIndex,int endIndex) {
		if (startIndex>=endIndex) {
			return null;
		}

		Object[] references = line.getReferences();
		int[] referenceIndices = line.getReferenceIndices();
		char[] text = line.getText();
		LineList list = line.getList();

		// Do the check for unknown Tokens first to avoid checking if Token objects are null
		for (int i=startIndex;i<endIndex;++i) {
			if (references[i]==null) {
				return null;
			}
		}

		IntList operatorStack = new IntList();
		IntList operatorPrecedences = new IntList();
		IntList RPNQueue = new IntList();
		ArrayList<Object> RPNObjectQueue = new ArrayList<Object>();

		// NOTE: A minus sign at the beginning is guaranteed to be unary
		// NOTE: When isMinusUnary is false, only a BinaryDirective, binary operator, or close bracket can appear
		// NOTE: When isMinusUnary is true, anything but a BinaryDirective, binary operator, or close bracket can appear
		boolean isMinusUnary = true;
		int index = startIndex;
		for (;index<endIndex;++index) {
			if (references[index] instanceof Number || references[index] instanceof Constant) {
				if (!isMinusUnary) {
					return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"This "+((references[index] instanceof Number)?"number":"constant")+" is following a number, constant, string literal, closing parenthesis, or sizeof/offset use, with no operator in between, which makes no sense.");
				}
				RPNQueue.add(index);
				RPNObjectQueue.add(references[index]);
				isMinusUnary = false;
			}
			// NOTE: The < > quotation marks don't make sense in immediate operands.
			else if (references[index]==Line.STRING_LITERAL_MARKER) {
				if (!isMinusUnary) {
					return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"This string literal is following a number, constant, string literal, closing parenthesis, or sizeof/offset use, with no operator in between, which makes no sense.");
				}
				int stringLength = line.getTokenColumn(index+1)-referenceIndices[index]-2;
				if (stringLength<1) {
					return new SyntaxError(line,referenceIndices[index],referenceIndices[index]+2,"The empty string doesn't make sense as a string literal in assembly language.");
				}
				if (stringLength>8) {
					return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"String literals in immediate (constant-value) expressions can only be up to and including 8 characters long, otherwise they would be longer than a qword.");
				}
				RPNQueue.add(index);
				RPNObjectQueue.add(references[index]);
				isMinusUnary = false;
			}
			else if (line.isOperator(index,'(')) {
				if (!isMinusUnary) {
					return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"This opening parenthesis is following a number, constant, string literal, closing parenthesis, or sizeof/offset use, with no operator in between, which makes no sense.");
				}
				operatorStack.add(index);
				operatorPrecedences.add(-1);	// Always want lowest precedence for brackets, so that nothing but a closing bracket will pop it
				isMinusUnary = true;
			}
			else if (line.isOperator(index,')')) {
				if (isMinusUnary) {
					return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"This closing parenthesis is either at the beginning of an expression, or following an operator, directive, or opening parenthesis, which makes no sense.");
				}
				// NOTE: Elements on the operator stack are negative if and only if they are unary minuses, in which case the actual index counts down from -1.
				while (!operatorStack.isEmpty() && !line.isOperator((operatorStack.getLast()>=0)?operatorStack.getLast():(-operatorStack.getLast()-1),'(')) {
					int operatorIndex = operatorStack.removeLast();
					int realOperatorIndex = (operatorIndex>=0)?operatorIndex:(-operatorIndex-1);
					RPNQueue.add(realOperatorIndex);
					RPNObjectQueue.add((operatorIndex!=realOperatorIndex)?Directive.UNARY_MINUS:references[realOperatorIndex]);
					operatorPrecedences.removeLast();
				}
				if (operatorStack.isEmpty()) {
					return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"This closing parenthesis has no matching opening parenthesis in this expression.");
				}
				// Remove the opening bracket
				operatorStack.removeLast();
				operatorPrecedences.removeLast();
				isMinusUnary = false;
			}
			else if (references[index]==Line.OPERATOR_MARKER || (references[index] instanceof Directive && references[index]!=Directive.OFFSET && references[index]!=Directive.SIZEOF)) {
				if (!isMinusUnary && references[index] instanceof Directive.UnaryDirective) {
					return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"This unary directive is following a number, constant, string literal, closing parenthesis, or sizeof/offset use, with no operator in between, which makes no sense.");
				}
				else if (isMinusUnary && references[index] instanceof Directive.BinaryDirective) {
					return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"This binary directive is either at the beginning of an expression, or following an operator, directive, or opening parenthesis, which makes no sense.");
				}

				int precedence;
				if (references[index] instanceof Directive) {
					precedence = ((Directive)references[index]).precedence();
				}
				else if (line.isOperator(index,'+')) {
					if (isMinusUnary) {
						continue;	// Skip over any unary plusses, since they are redundant
						// NOTE: isMinusUnary will stay true, which is valid
					}
					precedence = Directive.ADDITION_PRECEDENCE;
				}
				else if (line.isOperator(index,'-')) {
					precedence = (isMinusUnary) ?  Directive.UNARY_MINUS_PRECEDENCE : Directive.ADDITION_PRECEDENCE;
				}
				else if (line.isOperator(index,'*') || line.isOperator(index,'/')) {
					if (isMinusUnary) {
						return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"This binary operator is either at the beginning of an expression, or following an operator, directive, or opening parenthesis, which makes no sense.");
					}
					precedence = Directive.MULTIPLICATION_PRECEDENCE;
				}
				else {
					return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"The operator \""+new String(text,referenceIndices[index],line.getTokenColumn(index+1)-referenceIndices[index])+"\" doesn't make sense in this context.");
				}

				while (!operatorStack.isEmpty() && precedence<=operatorPrecedences.getLast()) {
					int operatorIndex = operatorStack.removeLast();
					int realOperatorIndex = (operatorIndex>=0)?operatorIndex:(-operatorIndex-1);
					RPNQueue.add(realOperatorIndex);
					RPNObjectQueue.add((operatorIndex!=realOperatorIndex)?Directive.UNARY_MINUS:references[realOperatorIndex]);
					operatorPrecedences.removeLast();
				}

				if (isMinusUnary && line.isOperator(index,'-')) {
					// NOTE: The index is stored going downward from -1 to denote that the minus is unary.
					operatorStack.add(-1-index);
				}
				else {
					operatorStack.add(index);
				}
				operatorPrecedences.add(precedence);

				isMinusUnary = true;
			}
			else if (references[index]!=Line.WHITE_SPACE_MARKER) {
				Object lastTokenObject = references[index];
				if (lastTokenObject==Directive.OFFSET) {
					if (!isMinusUnary) {
						return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"This unary directive is following a number, constant, string literal, closing parenthesis, or sizeof/offset use, with no operator in between, which makes no sense.");
					}
					do {
						if (index+1>=endIndex) {
							return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"This directive needs one operand and it has none.");
						}
						++index;
					} while (references[index]==Line.WHITE_SPACE_MARKER);
					Object object = references[index];
					if (!(object instanceof GlobalVariable || object instanceof Structure || object instanceof Function || object instanceof LineLabel)) {
						return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"The \"offset\" directive only accepts a global variable, function, line label, or structure member as its operand");
					}
					if (object instanceof GlobalVariable || object instanceof Function || object instanceof LineLabel) {
						RPNQueue.add(index);
						RPNObjectQueue.add(new Immediate(0,new Encoder.Relocation(line,(Symbol)object)));
						// There may be <variable>.<structure member>.<structure member> etc., but these don't change that it's a relocation
						while (index+2<endIndex && line.isOperator(index+1,'.') && references[index+2] instanceof StructureMember) {
							index += 2;
						}
					}
					else {	// object instanceof Structure
						Structure structure = (Structure)object;
						long memberOffset = 0;
						if (index+2>=endIndex) {
							return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"The \"offset\" directive only accepts a global variable, function, line label, or structure member as its operand.\nYou've supplied a structure, but no member of it.");
						}
						while (index+2<endIndex && line.isOperator(index+1,'.') && (references[index+2] instanceof StructureMember)) {
							StructureMember member = (StructureMember)references[index+2];
							memberOffset += structure.getMemberOffset(member);
							DataType memberType = DataTypeUtil.getDataType(member.getType().toString(),structure.getLanguage().getScope(),list.getParent().getScope(),list.getParent().getMode());
							structure = (memberType instanceof Structure) ? ((Structure)memberType) : null;
							index += 2;
						}
						RPNQueue.add(index);
						RPNObjectQueue.add(new Long(memberOffset));
					}
					isMinusUnary = false;
				}
				else if (lastTokenObject==Directive.SIZEOF) {
					if (!isMinusUnary) {
						return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"This unary directive is following a number, constant, string literal, closing parenthesis, or sizeof/offset use, with no operator in between, which makes no sense.");
					}
					do {
						if (index+1>=endIndex) {
							return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"This directive needs one operand and it has none.");
						}
						++index;
					} while (references[index]==Line.WHITE_SPACE_MARKER);
					Object obj = references[index];
					if (!(obj instanceof DataType || obj instanceof GlobalVariable || obj instanceof LocalVariable || obj instanceof Register)) {
						return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"The \"sizeof\" directive only accepts a data type, variable, or register as its operand");
					}
					if (obj instanceof Structure) {
						while (index+2<endIndex && line.isOperator(index+1,'.') && (references[index+2] instanceof StructureMember)) {
							obj = references[index+2];
							index += 2;
						}
					}
					RPNQueue.add(index);
					long size;
					if (obj instanceof DataType) {
						size = ((DataType)obj).getSize();
					}
					else if (obj instanceof StructureMember) {
						size = ((StructureMember)obj).getTotalSize();
					}
					else if (obj instanceof GlobalVariable) {
						size = ((GlobalVariable)obj).getSize();
					}
					else if (obj instanceof LocalVariable) {
						size = ((LocalVariable)obj).getTypeSize();
					}
					else { // obj instanceof Register
						size = DataTypeUtil.TYPE_MAP[((Register)obj).getType()].size>>>3;
					}
					RPNObjectQueue.add(new Long(size));
					isMinusUnary = false;
				}
				else {
					return new SyntaxError(line,referenceIndices[index],line.getTokenColumn(index+1),"Only numbers, constants, directives, and operators (+-*/) can be in immediate (constant-value) expressions, and \""+new String(text,referenceIndices[index],line.getTokenColumn(index+1)-referenceIndices[index])+"\" isn't one of those in this context.\nThe exceptions are data types after \"sizeof\" and global variables or structure members after \"offset\".");
				}
			}
		}

		// Add the remaining operators to the end of the queue
		for (int i=operatorStack.size()-1;i>=0;--i) {
			int operatorIndex = operatorStack.get(i);
			// NOTE: The index is stored going downward from -1 to denote if the operator is a unary minus.
			boolean isUnaryMinus = operatorIndex<0;
			if (isUnaryMinus) {
				operatorIndex = -operatorIndex-1;
			}
			if (line.isOperator(operatorIndex,'(')) {
				return new SyntaxError(line,line.getTokenColumn(operatorIndex),line.getTokenColumn(operatorIndex+1),"This opening parenthesis has no matching closing parenthesis in this expression.");
			}
			RPNQueue.add(operatorIndex);
			RPNObjectQueue.add(isUnaryMinus ? Directive.UNARY_MINUS : references[operatorIndex]);
		}
		if (RPNQueue.isEmpty()) {
			return new SyntaxError(line,startIndex,endIndex,"There's no value to evaluate here.");
		}
		operatorStack.clear();
		operatorPrecedences.clear();

		// NOTE: Because checks were made against the correct state of isMinusUnary earlier, any operators or directives are guaranteed to have enough operands
		ArrayList<Immediate> numberStack = new ArrayList<Immediate>();
		// Go through the queue and evaluate everything now that it's in Reverse Polish Notation order (postfix)
		for (int i=0;i<RPNQueue.size();++i) {
			int tokenIndex = RPNQueue.get(i);
			Object object = RPNObjectQueue.get(i);
			if (object instanceof Directive.BinaryDirective) {
				if (numberStack.size()<2) {
					return new SyntaxError(line,line.getTokenColumn(tokenIndex),line.getTokenColumn(tokenIndex+1),"This directive needs two operands and it has "+numberStack.size()+".");
				}
				Immediate right = numberStack.remove(numberStack.size()-1);
				Immediate left = numberStack.remove(numberStack.size()-1);
				if (left.relocation!=null || right.relocation!=null || left.diffRelocation!=null || right.diffRelocation!=null) {
					return new SyntaxError(line,line.getTokenColumn(tokenIndex),line.getTokenColumn(tokenIndex+1),"This directive can only be done on constants; offsets aren't supported for it yet.");
				}
				right.constant = ((Directive.BinaryDirective)object).value(left.constant,right.constant);
				numberStack.add(right);
			}
			else if (object instanceof Directive.UnaryDirective) {
				if (numberStack.size()<1) {
					return new SyntaxError(line,line.getTokenColumn(tokenIndex),line.getTokenColumn(tokenIndex+1),"This "+((object==Directive.UNARY_MINUS)?"operator":"directive")+" needs one operand and it has none.");
				}
				Immediate right = numberStack.remove(numberStack.size()-1);
				if (right.relocation!=null || right.diffRelocation!=null) {
					return new SyntaxError(line,line.getTokenColumn(tokenIndex),line.getTokenColumn(tokenIndex+1),"This "+((object==Directive.UNARY_MINUS)?"operator":"directive")+" can only be done on constants; offsets aren't supported for it yet.");
				}

				if (object!=Directive.UNARY_MINUS) {
					right.constant = ((Directive.UnaryDirective)object).value(right.constant);
					numberStack.add(right);
				}
				else {
					if (right.isDouble) {
						right.constant = Double.doubleToLongBits(-Double.longBitsToDouble(right.constant));
						numberStack.add(right);
					}
					else {
						right.constant = -right.constant;
						numberStack.add(right);
					}
				}
			}
			// NOTE: Unary minus is handled as a UnaryDirective instead of an operator, so all operators here are binary
			else if (object==Line.OPERATOR_MARKER) {
				if (numberStack.size()<2) {
					return new SyntaxError(line,line.getTokenColumn(tokenIndex),line.getTokenColumn(tokenIndex+1),"This operator needs two operands and it has "+numberStack.size()+".");
				}
				Immediate right = numberStack.remove(numberStack.size()-1);
				Immediate left = numberStack.remove(numberStack.size()-1);
				// Special cases for multiplying by zero or one, supported even if relocations
				if (line.isOperator(tokenIndex,'*') && (right.isZero() || left.isZero())) {
					numberStack.add(right.isZero()?right:left);
				}
				else if (line.isOperator(tokenIndex,'*') && (right.isOne() || left.isOne())) {
					numberStack.add(right.isOne()?left:right);
				}
				else if ((line.isOperator(tokenIndex,'*') || line.isOperator(tokenIndex,'/')) && (left.relocation!=null || right.relocation!=null || left.diffRelocation!=null || right.diffRelocation!=null)) {
					return new SyntaxError(line,line.getTokenColumn(tokenIndex),line.getTokenColumn(tokenIndex+1),"This operator can only be done on constants; offsets aren't supported for it yet.");
				}
				else if (line.isOperator(tokenIndex,'+') && ((left.relocation!=null && right.relocation!=null) || (left.diffRelocation!=null && right.diffRelocation!=null))) {
					return new SyntaxError(line,line.getTokenColumn(tokenIndex),line.getTokenColumn(tokenIndex+1),"Only up to one absolute offset and one difference of offsets are supported in a single immediate value.");
				}
				else if (line.isOperator(tokenIndex,'-') && ((left.relocation==null && right.relocation!=null) || (left.relocation!=null && right.relocation!=null && (left.diffRelocation!=null || right.diffRelocation!=null)) || (left.diffRelocation!=null && right.diffRelocation!=null))) {
					return new SyntaxError(line,line.getTokenColumn(tokenIndex),line.getTokenColumn(tokenIndex+1),"Only up to one absolute offset and one difference of offsets are supported in a single immediate value.");
				}
				else {
					// If one is a double, convert both to doubles
					if (left.isDouble && !right.isDouble) {
						right.constant = Double.doubleToLongBits(right.constant);
						right.isDouble = true;
					}
					else if (right.isDouble && !(left.isDouble)) {
						left.constant = Double.doubleToLongBits(left.constant);
						left.isDouble = true;
					}

					if (!(left.isDouble)) {
						if (line.isOperator(tokenIndex,'+')) {
							left.constant += right.constant;
						}
						else if (line.isOperator(tokenIndex,'-')) {
							left.constant -= right.constant;
						}
						else if (line.isOperator(tokenIndex,'*')) {
							left.constant *= right.constant;
						}
						else {	// line.isOperator(tokenIndex,'/')
							// TODO: Check whether this operator follows the correct convention for division (always positive remainder) or Java's convention for division (truncation toward zero), since in the former case, this must be adjusted for negatives
							left.constant /= right.constant;
						}
					}
					else {
						if (line.isOperator(tokenIndex,'+')) {
							left.constant = Double.doubleToLongBits(Double.longBitsToDouble(left.constant)+Double.longBitsToDouble(right.constant));
						}
						else if (line.isOperator(tokenIndex,'-')) {
							left.constant = Double.doubleToLongBits(Double.longBitsToDouble(left.constant)-Double.longBitsToDouble(right.constant));
						}
						else if (line.isOperator(tokenIndex,'*')) {
							left.constant = Double.doubleToLongBits(Double.longBitsToDouble(left.constant)*Double.longBitsToDouble(right.constant));
						}
						else {	// line.isOperator(tokenIndex,'/')
							left.constant = Double.doubleToLongBits(Double.longBitsToDouble(left.constant)/Double.longBitsToDouble(right.constant));
						}
					}
					if (line.isOperator(tokenIndex,'+')) {
						left.relocation = (left.relocation!=null) ? left.relocation : right.relocation;
						left.diffRelocation = (left.diffRelocation!=null) ? left.diffRelocation : right.diffRelocation;
					}
					else if (line.isOperator(tokenIndex,'-')) {
						if (right.diffRelocation!=null) {
							left.diffRelocation = right.diffRelocation;
							// Subtracting a difference switches the sides of the minus sign
							long temp = right.diffRelocation.symbolID;
							right.diffRelocation.symbolID = right.diffRelocation.negativeSymbolID;
							right.diffRelocation.negativeSymbolID = temp;
						}
						else if (left.relocation!=null && right.relocation!=null) {
							// Subtracting two absolute relocations creates a difference relocation
							left.relocation.type = ASMEncode.RELOC_DIFF;
							left.relocation.negativeSymbolID = right.relocation.symbolID;
							left.diffRelocation = left.relocation;
							left.relocation = null;
						}
					}
					numberStack.add(left);
				}
			}
			else if (object instanceof Long) {
				numberStack.add(new Immediate((Long)object));
			}
			else if (object instanceof Double) {
				numberStack.add(new Immediate(Double.doubleToLongBits((Double)object),true));
			}
			else if (object instanceof Constant) {
				Immediate immediate = ((Constant)object).getImmediate();
				if (immediate==null) {
					return new SyntaxError(line,line.getTokenColumn(tokenIndex),line.getTokenColumn(tokenIndex+1),"This constant doesn't have a known value.  It may be from a circular reference or syntax errors in the constant.\nIts text is \""+((Constant)object).getValue()+"\".");
				}
				numberStack.add(immediate.copy());
			}
			else if (object==Line.STRING_LITERAL_MARKER) {
				long value = 0;
				int tokenStart = line.getTokenColumn(tokenIndex);
				int tokenEnd = line.getTokenColumn(tokenIndex+1);
				char quotationMark = text[tokenStart++];
				--tokenEnd;	// The last will be the same quotation mark.
				int bitOffset = 0;
				// NOTE: This puts the characters in correct byte order, which is the opposite of what MASM does.
				for (int j=tokenStart;j<tokenEnd;++j) {
					value += (text[j]&0xFFL)<<bitOffset;
					// NOTE: If there's a character in the string literal that's the same as the starting quotation mark, it must be doubled as an escape character, so skip the second quotation mark.
					if (text[j]==quotationMark) {
						++j;
					}
					bitOffset += 8;
				}
				numberStack.add(new Immediate(value));
			}
			else if (object instanceof Immediate) {
				numberStack.add((Immediate)object);
			}
		}

		// There should always be exactly one element left in numberStack since checks were made against the correct state of isMinusUnary earlier
		return numberStack.get(0);
	}

	/**
	 * Evaluates the text representation of an array, structure, or array of structures
	 * @param line			the line containing the text to be evaluated
	 * @param type			the data type of the elements of the array or the {@link Structure}
	 * @param bytes			the list of bytes to be filled with the value of this array, or null if only the size is desired
	 * @param relocations	the list of relocations to be filled with any relocation entries in the value of this array, or null if only the size is desired
	 * @param startIndex	the starting token index (inclusive) of the immediate
	 * @param endIndex		the ending token index (exclusive) of the immediate
	 * @return an Integer with the length in bytes if the value is valid, null if there is an unknown token,
	 *         or {@link SyntaxError} if there is any syntax error listed for the line
	 */
	public Object evaluateArray(Line line, DataType type, ByteList bytes, ArrayList<Encoder.Relocation> relocations, int startIndex, int endIndex) {
		if (line.getNumErrors()>0) {
			return line.getErrors()[0];
		}
		Object[] refs = line.getReferences();
		// Check for null references first, so that it doesn't need to be done later.
		// NOTE: This may be done repeatedly for recursive calls, but this shouldn't be a huge concern, even if the user puts in some really crazy input, like:
		// 1 dup (1 dup (1 dup (1 dup (... 1 dup (12345)))))))))))))...))))))))))))))
		// In that case, the parsing takes O(n^2) anyway, so this check's time doesn't really matter.
		for (int i=startIndex;i<endIndex;++i) {
			if (refs[i]==null) {
				return null;
			}
		}
		final boolean justFindSize = (bytes==null);
		final int typeSize = type.getSize();
		final Scope globalScope = (line.getList()!=null && line.getList().getParent()!=null) ? line.getList().getParent().getGlobalScope() : null;
		final int currentMode = (line.getList()!=null && line.getList().getParent()!=null) ? line.getList().getParent().getMode() : Segment.DEFAULT_N_BITS;
		int totalSize = 0;
		boolean isNumericalType = !(type instanceof Structure);
		int index = startIndex;
		do {
			while (index<endIndex && refs[index]==Line.WHITE_SPACE_MARKER) {
				++index;
			}
			if (index==endIndex) {
				return new SyntaxError(line,line.getTokenColumn(startIndex),line.getTokenColumn(endIndex),"There's no value here, only emptiness...\nwow, that sounds so depressing, but you'll figure out what you want to put here. :)");
			}
			int notImmediateIndex = endIndex;
			boolean encounteredNonString = false;
			for (int i=index;i<endIndex;++i) {
				if (refs[i]==ASM.DUP || line.isOperator(i,',') || line.isOperator(i,'<')) {
					notImmediateIndex = i;
					break;
				}
				if (refs[i]!=Line.WHITE_SPACE_MARKER && refs[i]!=Line.STRING_LITERAL_MARKER) {
					encounteredNonString = true;
				}
			}
			if (refs[index]==ASM.UNDEFINED_VALUE) {
				if (!isNumericalType) {
					return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(index+1),"This represents a numerical undefined value, not a structure value.\nYou probably want to put \"<>\" here to indicate a structure with default initialization.");
				}
				totalSize += typeSize;
				if (!justFindSize) {
					for (int i=0;i<typeSize;++i) {
						// Just have the undefined value be zero, since it might as well be.
						// I could add in the bytes better than one by one, but not much better.
						bytes.add((byte)0);
					}
				}
				for (++index; index<endIndex && refs[index]==Line.WHITE_SPACE_MARKER;++index) {}
				if (index<endIndex && !line.isOperator(index,',')) {
					return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(index+1),"This doesn't make sense after an undefined value marker.  An undefined value can't be operated on, for example.");
				}
			}
			else if (index==notImmediateIndex && line.isOperator(index,',')) {
				return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(index+1),"You haven't specified a value here.  You might want to specify "+(isNumericalType?"an undefined value with \"?\".":"a structure having default initial values with \"<>\"."));
			}
			else if (index==notImmediateIndex && refs[index]==ASM.DUP) {
				return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(index+1),"You need to specify the number of repetitions before \"dup\".");
			}
			// String literal value
			else if (!encounteredNonString && refs[index]==Line.STRING_LITERAL_MARKER) {
				if (!isNumericalType) {
					return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(index+1),"This represents a string literal value, not a structure value.  Structure values are contained in < > brackets.");
				}
				if (type==ASM.REAL4 || type==ASM.REAL8 || type==ASM.REAL10) {
					return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(notImmediateIndex),"This is a floating-point type variable, so I'm not sure what it'd mean to have a string as its value.");
				}
				// NOTE: Having a WORD type string in MASM does not make the characters unicode, but I'm choosing to do that here because it's so useful.
				// NOTE: Having a string with type of more than 2 bytes in MASM has each "element" reversed in byte order, which is dumb.

				// NOTE: One can't just add the number of characters to totalSize, since "ab""cd" is the equivalent of "ab\"cd" in C or Java, which is only 5 characters, not 6.
				// NOTE: Despite the above, "ab''cd" is 6 characters, and 'ab''cd' is 5.  It's when the first character is duplicated that it's an escape sequence.
				char[] s = line.getText();
				char quotationMark = s[line.getTokenColumn(index)];
				int endOfQuotation = line.getTokenColumn(index+1)-1;
				int sizeToAdd = 0;
				for (int i = line.getTokenColumn(index)+1;i<endOfQuotation;++i) {
					while (i<endOfQuotation && s[i]!=quotationMark) {
						sizeToAdd += (typeSize==2) ? 2 : 1;
						if (!justFindSize) {
							if (typeSize!=2) {
								bytes.add((byte)s[i]);
							}
							else {
								bytes.addShort((short)s[i]);
							}
						}
						++i;
					}
					if (i+1<endOfQuotation && s[i+1]==quotationMark) {
						sizeToAdd += (typeSize==2) ? 2 : 1;
						if (!justFindSize) {
							if (typeSize!=2) {
								bytes.add((byte)quotationMark);
							}
							else {
								bytes.addShort((short)quotationMark);
							}
						}
						i+=2;
					}
				}
				// Round up to a multiple of typeSize.
				// NOTE: typeSize may not be a power of 2, because of the TBYTE type, so one can't use ((sizeToAdd+typeSize-1)&(~(typeSize-1)) - sizeToAdd.
				int extraBytes = ((sizeToAdd+typeSize-1)/typeSize)*typeSize - sizeToAdd;
				if (!justFindSize) {
					for (int i=0;i<extraBytes;++i) {
						bytes.add((byte)0);
					}
				}
				totalSize += sizeToAdd+extraBytes;

				index = notImmediateIndex;
				if (index<endIndex && !line.isOperator(index,',')) {
					return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(index+1),"This doesn't make sense after a string literal.  The only thing that works here is a comma, for more elements, or the end of the expression.");
				}
			}
			// If structure value definition
			else if (index==notImmediateIndex) { // && line.isOperator(index,'<')
				if (isNumericalType) {
					return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(index+1),"This represents a structure value, not a numerical value.");
				}
				Structure structure = (Structure)type;
				Language language = structure.getLanguage();
				if (language==null) {
					language = ASM.language;
				}
				Scope structureLanguageScope = language.getScope();
				++index;
				int innerEnd = index;
				int memberStartIndex = index;
				int memberNumber = 0;
				int bracketCount;
				int otherBracketCount = 0;
				for (bracketCount=1; bracketCount>0 && innerEnd<endIndex; ++innerEnd) {
					// NOTE: This doesn't need to keep track of quotation marks because they're already made into string literals.
					if (line.isOperator(innerEnd,'<')) {
						++bracketCount;
					}
					else if (line.isOperator(innerEnd,'>')) {
						--bracketCount;
					}
					else if (line.isOperator(innerEnd,'(')) {
						++otherBracketCount;
					}
					else if (line.isOperator(innerEnd,')')) {
						--otherBracketCount;
					}
					// NOTE: The innerEnd>index condition is because having an empty structure definition <> is valid, but having an empty member value isn't, e.g. <2,>
					if ((line.isOperator(innerEnd,',') && bracketCount==1 && otherBracketCount==0) || (bracketCount==0 && innerEnd>index)) {
						if (memberNumber==structure.getMembers().size()) {
							return new SyntaxError(line,line.getTokenColumn(memberStartIndex),line.getTokenColumn(innerEnd),"The structure "+structure.getName()+" only has "+memberNumber+" members.");
						}
						StructureMember member = structure.getMembers().get(memberNumber);
						DataType memberType = DataTypeUtil.getDataType(member.getType().toString(),structureLanguageScope,globalScope,currentMode);
						if (memberType==null) {
							return new SyntaxError(line,line.getTokenColumn(memberStartIndex),line.getTokenColumn(innerEnd),"This member ("+member.getName()+") of the structure "+structure.getName()+" doesn't have a valid data type, so that needs to be fixed.");
						}
						Object sizeOrError = evaluateArray(line,memberType,bytes,relocations,memberStartIndex,innerEnd);
						if (sizeOrError==null || sizeOrError instanceof SyntaxError) {
							return sizeOrError;
						}
						totalSize += (Integer)sizeOrError;
						memberStartIndex = innerEnd+1;
						++memberNumber;
					}
				}
				if (bracketCount>0) {
					return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(index+1),"This opening angle bracket has no closing angle bracket.");
				}
				// If not all members are specified, they should be filled with their default values.
				for (;memberNumber<structure.getMembers().size();++memberNumber) {
					StructureMember member = structure.getMembers().get(memberNumber);
					DataType memberType = DataTypeUtil.getDataType(member.getType().toString(),structureLanguageScope,globalScope,currentMode);
					if (memberType==null) {
						return new SyntaxError(line,line.getTokenColumn(memberStartIndex),line.getTokenColumn(innerEnd),"This member ("+member.getName()+") of the structure "+structure.getName()+" doesn't have a valid data type, so that needs to be fixed.");
					}
					ValueLine defaultValue = member.getInitialValue();
					Object sizeOrError = evaluateArray(defaultValue,memberType,bytes,relocations,0,defaultValue.getNumReferences());
					if (sizeOrError==null || sizeOrError instanceof SyntaxError) {
						return sizeOrError;
					}
					totalSize += (Integer)sizeOrError;
				}
				for (index = innerEnd+1; index<endIndex && refs[index]==Line.WHITE_SPACE_MARKER;++index) {}
				if (index<endIndex && !line.isOperator(index,',')) {
					return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(index+1),"This doesn't make sense after a \"number dup (expression)\" sequence.  The only thing that works here is a comma, for more elements, or the end of the expression.");
				}
			}
			// If immediate value appears first (possibly a value or preceding "dup")
			else {
				Object immediate = immediateShunting(line,index,notImmediateIndex);
				if (immediate==null || immediate instanceof SyntaxError) {
					return immediate;
				}
				if ((notImmediateIndex==endIndex || refs[notImmediateIndex]!=ASM.DUP) && !isNumericalType) {
					return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(notImmediateIndex),"This represents a numerical value, not a structure value.  Structure values are contained in < > brackets.");
				}
				else if (notImmediateIndex<endIndex && refs[notImmediateIndex]==ASM.DUP) {
					Immediate count = (Immediate)immediate;
					if (count.relocation!=null || count.diffRelocation!=null) {
						return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(notImmediateIndex),"Having an array size that depends on offsets of things isn't supported yet, and may not be any time soon,\nbecause it isn't useful for much and is a huge pain to support.  There are almost always ways of accomplishing the same thing,\neven if it may require run-time allocation or evaluation.");
					}
					if (count.isDouble) {
						return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(notImmediateIndex),"Having an array size that is a floating point value doesn't make too much sense to me,\nbut if you really want it, let me know what you'd like it to mean and I'll probably implement it.");
					}
					index = notImmediateIndex;
					for (++index;index<endIndex && refs[index]==Line.WHITE_SPACE_MARKER;++index) {}
					if (index+2>=endIndex || !line.isOperator(index,'(')) {
						return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(index+2),"\"dup\" must be followed by ( ) brackets containing one or more values to repeat.");
					}
					++index;
					int innerEnd = index+1;
					int bracketCount;
					for (bracketCount=1; bracketCount>0 && innerEnd<endIndex; ++innerEnd) {
						// NOTE: This doesn't need to keep track of quotation marks because they're already made into string literals.
						if (line.isOperator(innerEnd,'(')) {
							++bracketCount;
						}
						else if (line.isOperator(innerEnd,')')) {
							--bracketCount;
						}
					}
					if (bracketCount>0) {
						return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(index+1),"This opening parenthesis has no closing parenthesis.");
					}
					// Subtract 1 to make innerEnd refer to the index of the closing parenthesis.
					--innerEnd;

					ByteList innerBytes = (justFindSize ? null : new ByteList());
					ArrayList<Encoder.Relocation> innerRelocations = (justFindSize ? null : new ArrayList<Encoder.Relocation>(1));
					Object sizeOrError = evaluateArray(line,type,innerBytes,innerRelocations,index,innerEnd);
					if (sizeOrError==null || sizeOrError instanceof SyntaxError) {
						return sizeOrError;
					}
					int innerSize = ((Integer)sizeOrError);
					totalSize += count.constant*innerSize;
					if (!justFindSize) {
						for (int i=0;i<count.constant;++i) {
							int offset = bytes.size();
							bytes.addAll(innerBytes);
							for (Encoder.Relocation r : innerRelocations) {
								relocations.add(new Encoder.Relocation(line,r.type, r.offset + offset, r.symbolID, r.negativeSymbolID));
							}
						}
					}
					for (index = innerEnd+1; index<endIndex && refs[index]==Line.WHITE_SPACE_MARKER;++index) {}
					if (index<endIndex && !line.isOperator(index,',')) {
						return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(index+1),"This doesn't make sense after a \"number dup (expression)\" sequence.  The only thing that works here is a comma, for more elements, or the end of the expression.");
					}
				}
				else if (notImmediateIndex<endIndex && !line.isOperator(notImmediateIndex,',')) { // && isNumericalType
					return new SyntaxError(line,line.getTokenColumn(notImmediateIndex),line.getTokenColumn(notImmediateIndex+1),"This makes no sense after a numerical value.");
				}
				else { // isNumericalType && (notImmediateIndex==endIndex || line.isOperator(notImmediateIndex,','))
					Immediate value = (Immediate)immediate;
					if (!justFindSize) {
						if (value.relocation!=null) {
							value.relocation.offset = bytes.size();
							relocations.add(value.relocation);
						}
						if (value.diffRelocation!=null) {
							value.diffRelocation.offset = bytes.size();
							relocations.add(value.diffRelocation);
						}
					}
					// Write the value to bytes
					totalSize += typeSize;
					if (value.isDouble) {
						if (type!=ASM.REAL4 && type!=ASM.REAL8 && type!=ASM.REAL10) {
							return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(notImmediateIndex),"This is a floating-point value in a variable that's an integer type.  Change the type to Real4, Real8, or Real10\nfor 4-byte, 8-byte, or 10-byte floating point types.  This will probably just be a warning in the future, but\nI'd like to know whether people want these rounded or encoded as floating-point values.");
						}
						if (value.relocation!=null || value.diffRelocation!=null) {
							return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(notImmediateIndex),"This is a floating-point type variable, so I'm not sure what it'd mean to have an offset in its value.");
						}
						if (!justFindSize) {
							if (type==ASM.REAL4) {
								bytes.addInt(Float.floatToIntBits((float)Double.longBitsToDouble(value.constant)));
							}
							else if (type==ASM.REAL8) {
								bytes.addLong(value.constant);
							}
							else { // type==ASM.REAL10
								// Convert from 8-byte floating-point with 1 sign bit, 11 exponent bits, and 52 mantissa bits,
								// to 10-byte floating-point format with 1 sign bit, 15 exponent bits, and 64 mantissa bits
								long exponent = ((value.constant>>>52)&0x7FF)-0x3FF;
								long mantissa = value.constant&((1L<<52)-1);
								bytes.addLong(mantissa<<12);
								bytes.addShort((short)(exponent+0x3FFF+((value.constant<0)?0x8000:0)));
							}
						}
					}
					else {
						if ((type==ASM.REAL4 || type==ASM.REAL8 || type==ASM.REAL10) && (value.relocation!=null || value.diffRelocation!=null)) {
							return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(notImmediateIndex),"This is a floating-point type variable, so I'm not sure what it'd mean to have an offset in its value.");
						}
						if (typeSize<(DataTypeUtil.TYPE_MAP[DataTypeUtil.getOperandType(value.constant)].size>>>3)) {
							return new SyntaxError(line,line.getTokenColumn(index),line.getTokenColumn(notImmediateIndex),"The size of a "+type+" is "+typeSize+" bytes, and the constant you're trying to put into it is at least "+(DataTypeUtil.TYPE_MAP[DataTypeUtil.getOperandType(value.constant)].size>>>3)+" bytes.");
						}
						if (!justFindSize) {
							// TODO: Have some way of checking whether value.constant is a positive number >=2^63 instead of a negative number
							if (type==ASM.REAL4) {
								bytes.addInt(Float.floatToIntBits((float)value.constant));
							}
							else if (type==ASM.REAL8) {
								bytes.addLong(Double.doubleToLongBits((double)value.constant));
							}
							else if (type==ASM.REAL10) {
								if (value.constant==0) {
									bytes.addLong(0);
									bytes.addShort((short)0);
								}
								else {
									boolean sign = (value.constant<0);
									value.constant = Math.abs(value.constant);
									long exponent = 63;
									// Shift the value over until there's a 1 at the top, and count down the exponent
									do {
										value.constant += value.constant;
										--exponent;
									} while (value.constant>=0);
									// Shift the 1 off the top
									value.constant += value.constant;
									bytes.addLong(value.constant);
									bytes.addShort((short)(exponent+0x3FFF+(sign?0x8000:0)));
								}
							}
							else {
								int count = typeSize;
								while (count>0) {
									bytes.add((byte)(value.constant&0xFF));
									value.constant >>= 8;
									--count;
								}
							}
						}
					}
					index = notImmediateIndex;
				}
			}
			// NOTE: At this point, either index==endIndex or index refers to a comma.
			// Add 1 to index so that if there is a comma, it's skipped over it on the next iteration.
			++index;
		} while (index-1<endIndex);
		return totalSize;
	}
}
